package FileControl.Source;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

public class ThreadPool extends KillableThread implements Observer{

	private int size;
	private List<CopyThread> waitList;
	private List<CopyThread> active;
	private List<CopyThread> finished;
	
	private double rate;
	private long copied;
	private long toCopy;
	
	private static ThreadPool instance;
	
	private ThreadPool(int numThreads) {
		super();
		size = numThreads;
		waitList = new LinkedList<CopyThread>();
		active = new LinkedList<CopyThread>();
		finished = new LinkedList<CopyThread>();
	}
	
	public static ThreadPool getInstance() {
		if(instance == null) 
			poolSetup();
		return instance;
	}
	
	public static int getNumCores() {
		return Runtime.getRuntime().availableProcessors();
	}
	
	public static void poolSetup() {
		int size = getNumCores() * 2;
		instance = new ThreadPool(size);
	}
	
	public synchronized void addTask(CopyThread t) {
		waitList.add(t);
//		printSizes();
	}
	
	public void waitAvailable() {
		while(active.size() == size) {
			yield();
		}
	}
	
	public void waitDone() {
		while(active.size() > 0) {
			yield();
		}
	}
	
	public void run() {
		while(!kill || active.size() > 0 || waitList.size() > 0) {
//			System.out.println(".");
			//fill active list
			while (active.size() < size && waitList.size() > 0) {
				active.add(waitList.remove(0));
//				printStatus();
				
			}
			
			//check all threads in active are running, & remove finished
			for(int i=0; i<active.size(); i++) {
				CopyThread t = active.get(i);
				if(!t.isStarted() && !t.isAlive())
					t.start();
				else if(t.isFinished() || t.getState() == State.TERMINATED) { 
					active.remove(t);
					finished.add(t);
//					printStatus();
				}
			}
//			printStatus();
			yield();
		}
	}
	
	public void printStatus() {
		System.out.println("----Active Threads: " + active.size() + "----");
		for(int i=0; i<active.size(); i++) {
			CopyThread t = active.get(i);
			if(i < active.size()) 
				System.out.println(t.getSource().getName() + " - "+ t.getPercentDone() + "% \t " + this.bytesToStringWUnits(t.getSource().length())
						+ "\t" + this.millisToStringHMS((double)(t.size()-t.sizeCompleted)/rate));
		}
		System.out.println("-----------------------");
		//calculate total percent complete
		int total = waitList.size() + active.size() + finished.size();
		double percent = ((double)((int)( ((double)finished.size())/((double)total) * 10000)))/100;
		System.out.println("Total Completed: " + percent + "%");
		
		//calculate remaining time;
		displayTimeStats();
		//calculate remaining bytes;
		displayRemainingBytes();
		System.out.println("-----------------------");
	}
	
	public void displayRemainingBytes() {
		long bytes=0;
		for(int i=0; i<waitList.size(); i++) {
			CopyThread t = waitList.get(i);
			bytes+=t.getSource().length();
		}
		for(int i=0; i<active.size(); i++) {
			CopyThread t = active.get(i);
			bytes+=t.getSource().length()-t.sizeCompleted;
		}
		this.toCopy = bytes;
		System.out.println("To be copied: " + bytesToStringWUnits(bytes));
	}
	
	public synchronized void displayTimeStats() {
		//calculate elapsed time
		Date first = new Date();
		for(int i=0; i<finished.size(); i++) {
			CopyThread t = finished.get(i);
			if(t.getStartTime() < first.getTime())
				first = new Date(t.getStartTime());
		}
		for(int i=0; i<active.size(); i++) {
			CopyThread t = active.get(i);
			if(t.getStartTime() < first.getTime())
				first = new Date(t.getStartTime());
		}
		
		Date curr = new Date();
		long millis = curr.getTime() - first.getTime();
		System.out.println("Time Elapsed: " + millisToStringHMS(millis));
		
		//calculate total bytes copied
		long total = 0;
		for(int i=0; i<active.size(); i++) {
			CopyThread t = active.get(i);
			total += t.bytesCompleted();
		}
		for(int i=0; i<finished.size(); i++) {
			CopyThread t = finished.get(i);
			total += t.size();
		}
		System.out.println("Total bytes copied: " + bytesToStringWUnits(total));
		
		rate = (double)total / (double)millis;
		
		System.out.println("Copy rate: " + bytesToStringWUnits((long) (rate*(double)1000))+"/sec");
		
		//calculate remaining bytes
		long bytes=0;
		for(int i=0; i<waitList.size(); i++) {
			CopyThread t = waitList.get(i);
			bytes+=t.getSource().length();
		}
		for(int i=0; i<active.size(); i++) {
			CopyThread t = active.get(i);
			bytes += t.size() - t.sizeCompleted; 
		}
		
		double time =  (double)bytes / rate;
		System.out.println("Remaining time: "+ millisToStringHMS(time));
	}
	
	public void printSizes() {
		System.out.println("Waitlist: "+waitList.size()+"\nActive: "+active.size()+"\nFinished: "+finished.size());
	}
	
	public String millisToStringHMS(double millis) {
		double time = millis;
		int hours = (int) (time / (double)(1000*60*60));
		time = time % (double)(1000*60*60);
		int minutes = (int) (time / (double) (1000*60));
		time = time % (double)(1000*60);
		int secs = (int) (time / (double)(1000));
		return ""+hours + ":" + minutes + ":" + secs;
	}
	
	public String bytesToStringWUnits(long bytes) {
		String str = "";
		
		if(bytes < 1024) {
			str = "" + bytes + " b";
		} else if( bytes < 1048576) {
			str="" + ((double)((int)(((double)bytes / (double)1024) * 100))/(double)100)+ " Kb";
		} else if(bytes < 1073741824) {
			str ="" + ((double)((int)(((double)bytes / (double)1048576) * 100))/(double)100)+" Mb";
		} else {
			str="" + ((double)((int)(((double)bytes / (double)1073741824) * 100))/(double)100)+" Gb";
		}
		return str;
	}
	
	public String toString() {
		return "Thread Pool status: \n size: " + size + "\n active threads: " + active.size() + "\n waitList: " + waitList.size();
	}

	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		
	}
}
